package com.example.graph;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * 你这个学期必须选修 numCourses 门课程，记为 0 到 numCourses - 1 。
 * 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出，其中
 * prerequisites[i] = [ai, bi] ，表示如果要学习课程 ai 则 必须 先学习课程 bi 。
 * <p>
 * 例如，先修课程对 [0, 1] 表示：想要学习课程 0 ，你需要先完成课程 1 。
 * <p>
 * 请你判断是否可能完成所有课程的学习？如果可以，返回 true ；否则，返回 false 。
 * 示例 1：
 * <p>
 * 输入：numCourses = 2, prerequisites = [[1,0]]
 * 输出：true
 * 解释：总共有 2 门课程。学习课程 1 之前，你需要完成课程 0 。这是可能的。
 * <p>
 * 示例 2：
 * <p>
 * 输入：numCourses = 2, prerequisites = [[1,0],[0,1]]
 * 输出：false
 * 解释：总共有 2 门课程。学习课程 1 之前，你需要先完成课程 0 ；并且学习课程 0 之前，你还应先完成课程 1 。这是不可能的。
 * <p>
 * 提示：
 * <p>
 * 1 <= numCourses <= 105
 * 0 <= prerequisites.length <= 5000
 * prerequisites[i].length == 2
 * 0 <= ai, bi < numCourses
 * prerequisites[i] 中的所有课程对 互不相同
 */
public class Leetcode207_CanFinish {

    public static void main(String[] args) {

        int numCourses = 2;
        int[][] prerequisites = {{1, 0}, {0, 1}};

        System.out.println(new Solution().canFinish(numCourses, prerequisites));

    }

    static class Solution {

        /**
         * 拓扑排序(BFS)
         * 首先找到入度为0的节点将节点入队，并将指向这些入队为0的节点的邻接点的入度 -1
         * 将入度为0的节点出队，然后找新的入度为0的节点入队
         * 如此循环直到队列为空，
         *
         * @param numCourses
         * @param prerequisites
         * @return
         */
        public boolean canFinish(int numCourses, int[][] prerequisites) {
            List<List<Integer>> adj = new ArrayList<>();; // 索引为顶点，值为邻接点数组
            int[] indegresses = new int[numCourses]; // 入度数组
            // 初始化邻接表
            for (int i = 0; i < numCourses; ++i) {
                adj.add(new ArrayList<>());
            }
            // 初始化顶点的入度
            for (int[] info : prerequisites) {
                adj.get(info[1]).add(info[0]);
                ++indegresses[info[0]];
            }

            Queue<Integer> queue = new LinkedList<>();

            // 初始将入度为0的节点入队
            for (int i = 0; i < numCourses; ++i) {
                if (indegresses[i] == 0) {
                    queue.offer(i);
                }
            }
            int count= 0;
            // BFS
            while (!queue.isEmpty()) {
                ++count;
                int node = queue.poll();
                for (Integer neighbor : adj.get(node)) {
                    if (--indegresses[neighbor] == 0) { // 邻接点的入度等于0时将邻接点入队
                        queue.offer(neighbor);
                    }

                }
            }

            return count == numCourses;
        }
    }
}
